/**
 * @file msg.c
 * Implementation of the <b>Msg<b/> class.
 * FIXME test the implementation
 */

#include <string.h>
#include <stdlib.h>

#include <unistd.h>

#include "common.h"
#include "msg.h"
#include "net.h"

Msg *msg_new()
{
	Msg *msg = (Msg *)malloc(sizeof(Msg));
	if(!msg)
		return NULL;
	msg_clear(msg);
	return msg;

}

void msg_delete(Msg **msg)
{
	free(*msg);
	*msg = NULL;
}

void msg_clear(Msg *msg)
{
	if(!msg)
		return;
	memset(msg, 0, sizeof(Msg));
	msg->version = P2P_VERSION;
	msg->type = P2P_MSG_UNDEFINED;
	msg->ttl = P2P_MSG_TTL_DFL;
	msg->length = 0;
	addr_set(&msg->src, "0.0.0.0", 0, 0);
	addr_set(&msg->dst, "0.0.0.0", 0, 0);
}

void msg_cpy(Msg *dst, const Msg *src)
{
	if(!dst || !src)
		return;
	memcpy(dst, src, sizeof(Msg));
}

Msg *msg_dup(const Msg *msg)
{
	Msg *dup = msg_new();
	if(!dup)
		return NULL;
	msg_cpy(dup, msg);
	return dup;
}

void msg_set_hdr(Msg *msg, unsigned char version, unsigned char type, unsigned char ttl, const Addr *src, const Addr *dst)
{
	if(!msg)
		return;
	msg->version = version;
	msg->type = type;
	msg->ttl = ttl;
	msg->length = 0;
	addr_cpy(&msg->src, src);
	addr_cpy(&msg->dst, dst);
}

void msg_set_join_ack(Msg *msg, const Addr *left, const Addr *right)
{
	msg->type = P2P_MSG_JOIN_ACK;
	msg->length = 16;
	addr_cpy(&msg->d.join_ack.left, left);
	addr_cpy(&msg->d.join_ack.right, right);
}

void msg_set_link_up(Msg *msg, const Addr *neighbour, uint32_t side)
{
	msg->type = P2P_MSG_LINK_UPDATE;
	msg->length = 12;
	addr_cpy(&msg->d.link_up.neighbour, neighbour);
	msg->d.link_up.side = side;
}

void msg_set_search(Msg *msg, const Addr *bcast_init, uint32_t req_id, const char *file_name)
{
	msg->type = P2P_MSG_SEARCH;
	msg->length = 8+4+strlen(file_name)+1;
	addr_cpy(&msg->d.search.bcast_init, bcast_init);
	msg->d.search.req_id = req_id;
	strcpy(msg->d.search.file_name, file_name);
}

void msg_set_reply(Msg *msg, uint32_t req_id, uint32_t file_size)
{
	msg->type = P2P_MSG_REPLY;
	msg->length = 8;
	msg->d.reply.req_id = req_id;
	msg->d.reply.file_size = file_size;
}

void msg_set_neighbours_list(Msg *msg, Addr *neighbour1, Addr *neighbour2, char *node_name)
{
	msg->type = P2P_MSG_NEIGHBORS_LIST;
	msg->length = 4+8+8+strlen(node_name)+1;
	msg->d.neighbours_list.N = 2;
	addr_cpy(&msg->d.neighbours_list.neighbour1, neighbour1);
	addr_cpy(&msg->d.neighbours_list.neighbour2, neighbour2);
	strcpy(msg->d.neighbours_list.node_name, node_name);
}

void msg_set_get(Msg *msg, uint32_t begin, uint32_t end, char *file_name)
{
	msg->type = P2P_MSG_GET;
	msg->length = 4+4+strlen(file_name)+1;
	msg->d.get.begin = begin;
	msg->d.get.end = end;
	strcpy(msg->d.get.file_name, file_name);
}

void msg_set_data(Msg *msg, uint8_t status, uint32_t value)
{
	msg->type = P2P_MSG_DATA;
	msg->length = 4+4+(status == P2P_DATA_OK? value : 0);
	msg->d.data.status = status;
	msg->d.data.value = value;
}

int msg_hdr_read(const Node *node, Msg *msg, const char *buf)
{
	if(!node || !msg || !buf)
		return P2P_ERROR;
	/* Convert from network to host */
	msg->version = (buf[0] & 0xf0) >> 4;
	msg->type = buf[0] & 0x0f;
	msg->ttl = buf[1];
	memcpy(&msg->length, buf+2, 2);
	msg->length = ntohs(msg->length);
	addr_read(&msg->src, buf+4);
	addr_read(&msg->dst, buf+12);
	/* Do some checks */
	if(msg->version < 1 || msg->version > P2P_VERSION) {
		verbose(node, VERROR, "Unsupported protocol version: %d\n", msg->version);
		return P2P_ERROR;
	}
	if(msg->type != P2P_MSG_UNDEFINED 
	&& msg->type != P2P_MSG_JOIN_REQ 
	&& msg->type != P2P_MSG_JOIN_ACK 
	&& msg->type != P2P_MSG_LINK_UPDATE 
	&& msg->type != P2P_MSG_SEARCH 
	&& msg->type != P2P_MSG_REPLY 
	&& msg->type != P2P_MSG_NEIGHBORS_REQ 
	&& msg->type != P2P_MSG_NEIGHBORS_LIST 
	&& msg->type != P2P_MSG_GET 
	&& msg->type != P2P_MSG_DATA) {
		verbose(node, VERROR, "Unknown message type %d\n", msg->type);
		return P2P_ERROR;
	}
	if(msg->ttl < 1) {
		verbose(node, VPROTO, "TTL reached zero.\n");
		return P2P_ERROR;
	}
	if(msg->length < 0 || msg->length > P2P_MSG_MAX_DATA_SIZE) {
		verbose(node, VERROR, "Wrong message length %d\n", msg->length);
		return P2P_ERROR;
	}
	// Too complicated to check if ip.src == msg.src
	if(addr_cmp(&msg->dst, node->addr) && !addr_is_bcast(&msg->dst)) {
		verbose(node, VERROR, "Wrong message destination %s\n", addr_get_str(&msg->dst));
		return P2P_ERROR;
	}
	return P2P_OK;
}

void msg_hdr_write(const Msg *msg, char *buf)
{
	if(!buf || !msg)
		return;
	buf[0] = (msg->version & 0x0f) << 4;
	buf[0] |= msg->type & 0x0f;
	buf[1] = msg->ttl;
	uint16_t length = htons(msg->length); // length in network order
	memcpy(buf+2, &length, 2);
	addr_write(&msg->src, buf+4);
	addr_write(&msg->dst, buf+12);
}

int msg_data_read(const Node *node, Msg *msg, const char *buf)
{
	if(!node || !msg || !buf)
		return P2P_ERROR;

	switch(msg->type) {
	case P2P_MSG_JOIN_REQ: {
		if(msg->length != 0)
			verbose(node, VDEBUG, "Warning! P2P_JOIN_REQ has non-zero length. Ignoring data field.\n");
		break;
	}
	case P2P_MSG_JOIN_ACK: {
		if(msg->length != 16) {
			verbose(node, VDEBUG, "Error! P2P_JOIN_ACK has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		addr_read(&msg->d.join_ack.left, buf);
		addr_read(&msg->d.join_ack.right, buf+8);
		break;
	}
	case P2P_MSG_LINK_UPDATE: {
		if(msg->length != 12) {
			verbose(node, VDEBUG, "Error! P2P_LINK_UP has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		addr_read(&msg->d.link_up.neighbour, buf);
		memcpy(&msg->d.link_up.side, buf+8, 4);
		msg->d.link_up.side = ntohl(msg->d.link_up.side);
		if(msg->d.link_up.side != LEFT && msg->d.link_up.side != RIGHT) {
			verbose(node, VDEBUG, "Error! P2P_LINK_UP has invalid neighbour type %d. Discarded.\n", msg->d.link_up.side);
			return P2P_ERROR;
		}
		break;
	}
	case P2P_MSG_SEARCH: {
		if(msg->length < 12+1) { // don't allow empty file_name
			verbose(node, VDEBUG, "Error! P2P_SEARCH has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		addr_read(&msg->d.search.bcast_init, buf);
		memcpy(&msg->d.search.req_id, buf+8, 4);
		msg->d.search.req_id = ntohl(msg->d.search.req_id);
		memcpy(msg->d.search.file_name, buf+8+4, msg->length-8-4); //FIXME copy 0B
		msg->d.search.file_name[msg->length-8-4] = '\0';
		break;
	}
	case P2P_MSG_REPLY: {
		if(msg->length != 8) {
			verbose(node, VDEBUG, "Error! P2P_REPLY has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		memcpy(&msg->d.reply.req_id, buf, 4);
		msg->d.reply.req_id = ntohl(msg->d.reply.req_id);
		memcpy(&msg->d.reply.file_size, buf+4, 4);
		msg->d.reply.file_size = ntohl(msg->d.reply.file_size);
		break;
	}
	case P2P_MSG_NEIGHBORS_REQ : {
		if(msg->length != 8) {
			verbose(node, VDEBUG, "Error! P2P_NEIGHBORS_REQ has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		addr_read(&msg->d.neighbours_req.bcast_init, buf);
		break;
	}
	case P2P_MSG_NEIGHBORS_LIST: {
		if(msg->length < 4) {
			verbose(node, VDEBUG, "Error! P2P_NEIGHBORS_REQ has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		msg->d.neighbours_list.N = buf[0];
		if(msg->d.neighbours_list.N != 2) {
			verbose(node, VDEBUG, "Error! P2P_NEIGHBORS_LIST has %d neighbour(s). Discarded.\n", msg->d.neighbours_list.N);
			return P2P_ERROR;
		}
		addr_read(&msg->d.neighbours_list.neighbour1, buf+4);
		addr_read(&msg->d.neighbours_list.neighbour2, buf+12);
		memcpy(msg->d.neighbours_list.node_name,
			buf+4+msg->d.neighbours_list.N*8,
			msg->length-4-msg->d.neighbours_list.N*8); //FIXME copy 0B
		break;
	}
	case P2P_MSG_GET: {
		if(msg->length < 8+1) { // don't allow empty filename
			verbose(node, VDEBUG, "Error! P2P_MSG_GET has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		memcpy(&msg->d.get.begin, buf, 4);
		msg->d.get.begin = ntohl(msg->d.get.begin);
		memcpy(&msg->d.get.end, buf+4, 4);
		msg->d.get.end = ntohl(msg->d.get.end);
		memcpy(msg->d.get.file_name, buf+4+4, msg->length-4-4);
		msg->d.get.file_name[msg->length-4-4] = '\0';
		break;
	}
	case P2P_MSG_DATA: {
		if(msg->length < 8) {
			verbose(node, VDEBUG, "Error! P2P_MSG_DATA has length %d. Discarded.\n", msg->length);
			return P2P_ERROR;
		}
		msg->d.data.status = buf[0];
		if(msg->d.data.status != P2P_DATA_OK && msg->d.data.status != P2P_DATA_ERROR) {
			verbose(node, VDEBUG, "Error! P2P_MSG_DATA has unknown status code %d\n", msg->d.data.status);
			return P2P_ERROR;
		}
		memcpy(&msg->d.data.value, buf+4, 4);
		msg->d.data.value = ntohl(msg->d.data.value);
		if(msg->d.data.status == P2P_DATA_OK
		&& (msg->d.data.value < 1 || msg->d.data.value != msg->length-8)) {
			verbose(node, VDEBUG, "Error! P2P_MSG_DATA has size field %d. Expecting DATA_LENGTH-8 bytes, meaning %d.\n",
				msg->d.data.value,
				msg->length-8);
			return P2P_ERROR;
		}
		if(msg->d.data.status == P2P_DATA_ERROR
		&& msg->d.data.value != P2P_BAD_REQUEST
		&& msg->d.data.value != P2P_UNAUTHORIZED
		&& msg->d.data.value != P2P_FORBIDDEN
		&& msg->d.data.value != P2P_NOT_FOUND
		&& msg->d.data.value != P2P_GONE
		&& msg->d.data.value != P2P_REQUEST_URI_TOO_LONG
		&& msg->d.data.value != P2P_INTERNAL_SERVER_ERROR
		&& msg->d.data.value != P2P_NOT_IMPLEMENTED
		&& msg->d.data.value != P2P_SERVICE_UNAVAILABLE) {
			verbose(node, VDEBUG, "Error! P2P_MSG_DATA has unknown error %d. Discarded.\n", msg->d.data.value);
			return P2P_ERROR;
		}
		if(msg->d.data.status == P2P_DATA_OK)
			memcpy(msg->d.data.data, buf+8, msg->d.data.value);
		break;
	}
	default:
		verbose(node, VPROTO, "Received unknown message. Discarded.\n");
		return P2P_ERROR;
		break;
	}

	return P2P_OK;
}

int msg_data_write(const Msg *msg, char *buf)
{
	if(!msg || !buf)
		return P2P_ERROR;

	switch(msg->type) {
	case P2P_MSG_JOIN_REQ: {
		//msg->length = 0;
		break;
	}
	case P2P_MSG_JOIN_ACK: {
		//msg->length = 16;
		addr_write(&msg->d.join_ack.left, buf);
		addr_write(&msg->d.join_ack.right, buf+8);
		break;
	}
	case P2P_MSG_LINK_UPDATE: {
		//msg->length = 12;
		addr_write(&msg->d.link_up.neighbour, buf);
		uint32_t side = htonl(msg->d.link_up.side);
		memcpy(buf+8, &side, 4);
		break;
	}
	case P2P_MSG_SEARCH: {
		/*
		msg->length = 12+strlen(msg->d.search.file_name)+1;
		if(msg->length > P2P_MSG_MAX_DATA_SIZE)
			return P2P_ERROR;
			*/
		addr_write(&msg->d.search.bcast_init, buf);
		uint32_t req_id = htonl(msg->d.search.req_id);
		memcpy(buf+8, &req_id, 4);
		strcpy(buf+12, msg->d.search.file_name);
		break;
	}
	case P2P_MSG_REPLY: {
		//msg->length = 8;
		;
		uint32_t req_id = htonl(msg->d.reply.req_id);
		memcpy(buf, &req_id, 4);
		uint32_t file_size = htonl(msg->d.reply.file_size);
		memcpy(buf+4, &file_size, 4);
		break;
	}
	case P2P_MSG_NEIGHBORS_REQ: {
		//msg->length = 8;
		addr_write(&msg->d.neighbours_req.bcast_init, buf);
		break;
	}
	case P2P_MSG_NEIGHBORS_LIST: {
		/*
		msg->length = 4+8+8+strlen(msg->d.neighbours_list.node_name)+1;
		if(msg->length > P2P_MSG_MAX_DATA_SIZE)
			return P2P_ERROR;
			*/
		buf[0] = msg->d.neighbours_list.N;
		buf[1] = buf[2] = buf[3] = 0;
		addr_write(&msg->d.neighbours_list.neighbour1, buf+4);
		addr_write(&msg->d.neighbours_list.neighbour2, buf+12);
		strcpy(buf+20, msg->d.neighbours_list.node_name);
		break;
	}
	case P2P_MSG_GET: {
		/*
		msg->length = 4+4+strlen(msg->d.get.file_name)+1;
		if(msg->length > P2P_MSG_MAX_DATA_SIZE)
			return P2P_ERROR;
			*/
		;
		uint32_t begin = htonl(msg->d.get.begin);
		memcpy(buf, &begin, 4);
		uint32_t end = htonl(msg->d.get.end);
		memcpy(buf+4, &end, 4);
		strcpy(buf+8, msg->d.get.file_name);
		break;
	}
	case P2P_MSG_DATA: {
		/*
		msg->length = 4+4+(msg->d.data.status == P2P_DATA_OK? msg->d.data.value : 0);
		if(msg->length > P2P_MSG_MAX_DATA_SIZE)
			return P2P_ERROR;
			*/
		buf[0] = msg->d.data.status;
		buf[1] = buf[2] = buf[3] = 0;
		uint32_t value = htonl(msg->d.data.value);
		memcpy(buf+4, &value, 4);
		if(msg->d.data.status == P2P_DATA_OK)
			memcpy(buf+8, msg->d.data.data, msg->d.data.value);
		break;
	}
	default:
		return P2P_ERROR;
		break;
	}

	return P2P_OK;
}

int msg_read_tcp(Node *node, Msg *msg, int sock)
{
	if(!node || !msg)
		return P2P_ERROR;

	char buf[P2P_HDR_SIZE+P2P_MSG_MAX_DATA_SIZE];
	/* Read the header */
	if(readt(node, sock, buf, P2P_HDR_SIZE, 1) == P2P_ERROR)
		return P2P_ERROR;
	if(msg_hdr_read(node, msg, buf) == P2P_ERROR)
		return P2P_ERROR;
	/* Read the payload */
	if(readt(node, sock, buf+P2P_HDR_SIZE, msg->length, 1) == P2P_ERROR)
		return P2P_ERROR;
	if(msg_data_read(node, msg, buf+P2P_HDR_SIZE) == P2P_ERROR)
		return P2P_ERROR;
	/* Be verbose */
	msg_dump(node, msg);

	return P2P_OK;
}

int msg_read_udp(Node *node, Msg *msg, int sock)
{
	if(!node || !msg)
		return P2P_ERROR;
	/* Read all the message */
	char buf[P2P_HDR_SIZE+P2P_MSG_MAX_DATA_SIZE];
	int read = recvfrom(sock, buf, sizeof(buf), 0, NULL, 0);
	if(read < P2P_HDR_SIZE) {
		verbose(node, VERROR, "Header too short.\n");
		return P2P_ERROR;
	}
	if(msg_hdr_read(node, msg, buf) == P2P_ERROR)
		return P2P_ERROR;
	if(msg_data_read(node, msg, buf+P2P_HDR_SIZE) == P2P_ERROR)
		return P2P_ERROR;
	/* Be verbose */
	msg_dump(node, msg);

	return P2P_OK;
}

int msg_write_tcp(Node *node, const Msg *msg, int sock)
{
	if(!msg)
		return P2P_ERROR;
	msg_dump(node, msg);

	char buf[P2P_HDR_SIZE+msg->length];
	msg_hdr_write(msg, buf);
	msg_data_write(msg, buf+P2P_HDR_SIZE);
	return writet(node, sock, buf, P2P_HDR_SIZE+msg->length, 5); // FIXME timeout
}

int msg_write_udp(Node *node, const Msg *msg, int sock)
{
	if(!msg)
		return P2P_ERROR;
	msg_dump(node, msg);

	char buf[P2P_HDR_SIZE+msg->length];
	msg_hdr_write(msg, buf);
	msg_data_write(msg, buf+P2P_HDR_SIZE);
	sendto(sock, buf, sizeof(buf), 0, NULL, 0);
	return P2P_OK; //FIXME
}

int msg_send_tcp(Node *node, const Msg *msg)
{
	int sock;
	if((sock = tcp_socket_create(node, &msg->dst)) == -1)
		return P2P_ERROR;
	if(msg_write_tcp(node, msg, sock) == P2P_ERROR)
		return P2P_ERROR;
	return socket_close(node, sock);
}

int msg_send_udp(Node *node, const Msg *msg)
{
	int sock;
	if((sock = udp_socket_create(node, &msg->dst)) == -1)
		return P2P_ERROR;
	if(msg_write_udp(node, msg, sock) == P2P_ERROR)
		return P2P_ERROR;
	return socket_close(node, sock);
}

void msg_rebroadcast(Node *node, const Msg *msg)
{
	if(!node || !msg)
		return;
	if(!addr_is_bcast(&msg->dst))
		return;
	if(msg->ttl <= 1)
		return;
	Addr *dst = addr_cmp(node->addr_left, &msg->src) == 0? node->addr_right : node->addr_left;
	//FIXME this should work, since bcast_init is the first member for both SEARCH and NEIGH_LIST
	if(addr_cmp(dst, &msg->d.search.bcast_init) == 0)
		return;
	if(addr_cmp(dst, node->addr) == 0)
		return;
	Msg *bcast = msg_dup(msg);
	bcast->ttl--;
	addr_cpy(&bcast->src, node->addr);
	msg_send_udp(node, bcast);
	msg_delete(&bcast);
}

void msg_dump(Node *node, const Msg *msg)
{
	/* Dump header */
	verbose(node, VMSG, "================================================================\n");
	verbose(node, VMSG, "version=%d, type=%d, ttl=%d, length=%d\n", msg->version, msg->type, msg->ttl, msg->length);
	verbose(node, VMSG, "src=%s\n", addr_get_str(&msg->src));
	verbose(node, VMSG, "dst=%s\n", addr_get_str(&msg->dst));
	verbose(node, VMSG, "----------------------------------------------------------------\n");
	/* Dump data */
	switch(msg->type) {
	case P2P_MSG_JOIN_REQ: {
		verbose(node, VMSG, "JOIN_REQ\n");
		break;
	}
	case P2P_MSG_JOIN_ACK: {
		verbose(node, VMSG, "P2P_JOIN_ACK\n");
		verbose(node, VMSG, "left=%s\n", addr_get_str(&msg->d.join_ack.left));
		verbose(node, VMSG, "right=%s\n", addr_get_str(&msg->d.join_ack.right));
		break;
	}
	case P2P_MSG_LINK_UPDATE: {
		verbose(node, VMSG, "P2P_LINK_UPDATE\n");
		verbose(node, VMSG, "neighbour=%s\n", addr_get_str(&msg->d.link_up.neighbour));
		verbose(node, VMSG, "side=%s\n", msg->d.link_up.side == LEFT? "LEFT" : "RIGHT");
		break;
	}
	case P2P_MSG_SEARCH: {
		verbose(node, VMSG, "P2P_SEARCH\n");
		verbose(node, VMSG, "bcast_init=%s\n", addr_get_str(&msg->d.search.bcast_init));
		verbose(node, VMSG, "req_id=%d\n", msg->d.search.req_id);
		verbose(node, VMSG, "file_name=\"%s\"\n", msg->d.search.file_name);
		break;
	}
	case P2P_MSG_REPLY: {
		verbose(node, VMSG, "P2P_REPLY\n");
		verbose(node, VMSG, "req_id=%d\n", msg->d.reply.req_id);
		verbose(node, VMSG, "file_size=%d\n", msg->d.reply.file_size);
		break;
	}
	case P2P_MSG_NEIGHBORS_REQ: {
		verbose(node, VMSG, "P2P_NEIGHBORS_REQ\n");
		verbose(node, VMSG, "bcast_init=%s\n", addr_get_str(&msg->d.neighbours_req.bcast_init));
		break;
	}
	case P2P_MSG_NEIGHBORS_LIST: {
		verbose(node, VMSG, "P2P_NEIGHBORS_LIST\n");
		verbose(node, VMSG, "N=%d\n", msg->d.neighbours_list.N);
		verbose(node, VMSG, "neighbour1=%s\n", addr_get_str(&msg->d.neighbours_list.neighbour1));
		verbose(node, VMSG, "neighbour2=%s\n", addr_get_str(&msg->d.neighbours_list.neighbour2));
		verbose(node, VMSG, "node_name=\"%s\"\n", msg->d.neighbours_list.node_name);
		break;
	}
	case P2P_MSG_GET: {
		verbose(node, VMSG, "P2P_GET\n");
		verbose(node, VMSG, "begin=%d\n", msg->d.get.begin);
		verbose(node, VMSG, "end=%d\n", msg->d.get.end);
		verbose(node, VMSG, "file_name=\"%s\"\n", msg->d.get.file_name);
		break;
	}
	case P2P_MSG_DATA: {
		verbose(node, VMSG, "P2P_DATA\n");
		verbose(node, VMSG, "status=%d\n", msg->d.data.status);
		verbose(node, VMSG, "value=%d\n", msg->d.data.value);
		break;
	}
	default:
		break;
	}
	verbose(node, VMSG, "================================================================\n");
}
